package testing

import (
	"context"
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"

	"xagent/internal/config"
	"xagent/internal/memory"
	"xagent/internal/monitoring"
	"xagent/internal/providers"
	"xagent/internal/types"
)

// TestContext provides context for tests
type TestContext struct {
	Config     *config.Config
	Monitoring *monitoring.Server
	cleanupFns []func()
	mutex      sync.Mutex
}

// NewTestContext creates a new test context
func NewTestContext(t *testing.T) *TestContext {
	ctx := &TestContext{
		cleanupFns: make([]func(), 0),
	}

	// Load test configuration
	cfg := &config.Config{}
	cfg.Server.Port = 8081 // Use different port for tests
	ctx.Config = cfg

	// Start monitoring server
	monitor := monitoring.NewServer(cfg.Server.Port)
	if err := monitor.Start(); err != nil {
		t.Fatalf("Failed to start monitoring server: %v", err)
	}
	ctx.Monitoring = monitor

	// Add cleanup function
	ctx.AddCleanup(func() {
		if err := monitor.Stop(context.Background()); err != nil {
			t.Logf("Failed to stop monitoring server: %v", err)
		}
	})

	return ctx
}

// AddCleanup adds a cleanup function
func (c *TestContext) AddCleanup(fn func()) {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	c.cleanupFns = append(c.cleanupFns, fn)
}

// Cleanup runs all cleanup functions
func (c *TestContext) Cleanup() {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	for i := len(c.cleanupFns) - 1; i >= 0; i-- {
		c.cleanupFns[i]()
	}
}

// MockLLMProvider is a mock LLM provider for testing
type MockLLMProvider struct {
	mock.Mock
}

func (m *MockLLMProvider) Complete(ctx context.Context, prompt string, options *providers.CompletionOptions) (string, error) {
	args := m.Called(ctx, prompt, options)
	return args.String(0), args.Error(1)
}

func (m *MockLLMProvider) Embed(ctx context.Context, text string) ([]float32, error) {
	args := m.Called(ctx, text)
	return args.Get(0).([]float32), args.Error(1)
}

func (m *MockLLMProvider) Stream(ctx context.Context, prompt string, options *providers.CompletionOptions) (<-chan string, error) {
	args := m.Called(ctx, prompt, options)
	return args.Get(0).(<-chan string), args.Error(1)
}

func (m *MockLLMProvider) GetName() string {
	args := m.Called()
	return args.String(0)
}

// MockMemoryManager is a mock memory manager for testing
type MockMemoryManager struct {
	mock.Mock
}

func (m *MockMemoryManager) Store(ctx context.Context, entry *memory.Entry) error {
	args := m.Called(ctx, entry)
	return args.Error(0)
}

func (m *MockMemoryManager) Retrieve(ctx context.Context, memType types.MemoryType, limit int) ([]*memory.Entry, error) {
	args := m.Called(ctx, memType, limit)
	return args.Get(0).([]*memory.Entry), args.Error(1)
}

func (m *MockMemoryManager) Search(ctx context.Context, query string, limit int) ([]*memory.Entry, error) {
	args := m.Called(ctx, query, limit)
	return args.Get(0).([]*memory.Entry), args.Error(1)
}

func (m *MockMemoryManager) Delete(ctx context.Context, id string) error {
	args := m.Called(ctx, id)
	return args.Error(0)
}

// MockResourceManager is a mock resource manager for testing
type MockResourceManager struct {
	mock.Mock
}

func (m *MockResourceManager) AcquireResource(id string) bool {
	args := m.Called(id)
	return args.Bool(0)
}

func (m *MockResourceManager) ReleaseResource(id string) {
	m.Called(id)
}

// AssertEventually asserts that a condition becomes true within a timeout
func AssertEventually(t *testing.T, condition func() bool, timeout time.Duration, interval time.Duration, msgAndArgs ...interface{}) bool {
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		if condition() {
			return true
		}
		time.Sleep(interval)
	}
	return assert.Fail(t, fmt.Sprintf("Condition not met within %v", timeout), msgAndArgs...)
}

// AssertMetric asserts that a metric has a specific value
func AssertMetric(t *testing.T, name string, labels map[string]string, expected float64) bool {
	// TODO: Implement metric value retrieval and comparison
	return true
}

// WaitForMetric waits for a metric to reach a specific value
func WaitForMetric(t *testing.T, name string, labels map[string]string, expected float64, timeout time.Duration) bool {
	return AssertEventually(t, func() bool {
		return AssertMetric(t, name, labels, expected)
	}, timeout, 100*time.Millisecond)
}
